// CyTargetLinker RIN conversion,
//
// scripts to create RIN network files for several online databases
// can be used with the CyTargetLinker app for Cytoscape
//
// Copyright 2011-2013 Department of Bioinformatics - BiGCaT, Maastricht University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//


package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.bridgedb.DataSource;
import org.bridgedb.IDMapperStack;

import cytargetlinker.conversion.ArgsParser.AFilesAttributes;
import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.data.GeneNode;
import cytargetlinker.conversion.data.MTI;
import cytargetlinker.conversion.data.MiRNANode;
import cytargetlinker.conversion.graph.Graph;

/**
 * Converts miRNA target text files from MiRecords to a XGMML or GML network.
 * 
 * The input files for this script can be downloaded here:
 * http://mirecords.biolead.org/download_data.php?v=2
 * 
 * @author Thomas, Pooja, Naime, martina
 */
public class MiRecords {

	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	static Args pargs;
	private interface Args extends AHelp, AFilesIn, AFilesOut, AFilesAttributes {}

	/**
	 * USAGE: java -cp conversion.jar cytargetlinker.conversion.MiRecords
	 * 
	 * REQUIRED ARGUMENTS:
	 * [-i] = input file path
	 * [-o] = output file path
	 * [-m] = list of bridgedb files for identifier mapping
	 * [-l] = log file path
	 * [--organism] = scientific name for the organism
	 *  
	 * OPTIONAL ARGUMENTS:
	 * [-n] = network name
	 * 
	 */
	public static void main(String argv[]) throws Exception {

		pargs = ArgsParser.parse(argv, Args.class);

		MiRecords converter = new MiRecords();
		converter.startConversion(pargs);
	}

	private IDMapperStack idMapper;
	private Graph graph;
	private Map<String, Integer> index;
	
	private Map<String, MiRNANode> miRNAs;
	private Map<String, GeneNode> genes;
	private Map<String, MTI> interactions;
	
	private List<String> nodesNotFound;
	private List<String> interactionIgnored;

	public MiRecords() {
		index = new HashMap<String, Integer>();
		
		miRNAs = new HashMap<String, MiRNANode>();
		genes = new HashMap<String, GeneNode>();
		interactions = new HashMap<String, MTI>();
		nodesNotFound = new ArrayList<String>();
		interactionIgnored = new ArrayList<String>();
	}

	public void startConversion(Args pargs) throws Exception {
		File in = pargs.getInput();
		if(in != null) {
			Utils.setUpLogger(log, getLogFile(), true);
			idMapper = Utils.initIDMapper(pargs, false);
			if(idMapper == null) {
				log.severe("no bridgedb file specified");
			} else {
				if (pargs.getOrganism() != null) {
					log.info("conversion of MiRecords file started ...\n");
					ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
						public Graph buildGraph(File in) throws Exception {
							return importMiRecords(in);
						}
					});
		
					log.info("miRecords network file created.\n\n");
				} else {
					log.severe("Please specify organism. --organism Homo sapiens or Mus musculus.\n");
				}
			}
		} else {
			System.out.println("Please specify input file!");
		}
	}

	public Graph importMiRecords(File input) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader(input));
		String[] header = br.readLine().split("\t");

		for (int i = 0; i < header.length; i++)
			index.put(header[i], i);

		graph = new Graph();
		setNetworkAttributes(input);

		// Load each line into memory
		String line = null;
		List<String[]> rows = new ArrayList<String[]>();
		while ((line = br.readLine()) != null) {
			line = removeInvalidXMLCharacters(line);
			String[] str = line.split("\t", header.length);
			if (str[index.get("Target gene_species_scientific")].equals(pargs.getOrganism())) {
				rows.add(line.split("\t", header.length));
			}
		}

		for (String[] row : rows) {
			String geneNode = createGeneNode(row);
			String miRNANode = createMiRNANode(row);
			
			if(geneNode != null && miRNANode != null) {
				if(!interactions.containsKey(miRNANode + "_" + geneNode)) {
					MTI mti = new MTI(miRNAs.get(miRNANode), genes.get(geneNode), "", "", row[index.get("Pubmed_id")]);
					interactions.put(miRNANode + "_" + geneNode, mti);
				}
			} else {
				if(!interactionIgnored.contains(row[index.get("miRNA_mature_ID")] + " -> " + row[index.get("Target gene_Refseq_acc")])) {
					interactionIgnored.add(row[index.get("miRNA_mature_ID")] + " -> " + row[index.get("Target gene_Refseq_acc")]);
				}
			}
		}
			
		int count = 0;
		for(String str : interactions.keySet()) {
			MTI mti = interactions.get(str);
			mti.createEdge(graph, "miRecords 2010-11-25", count);
			count++;
		}

		log.info(interactions.size() + " interactions have been found.\n" + genes.size() + " gene nodes.\n" + miRNAs.size() + " miRNA nodes.\n");
		log.info(interactionIgnored.size() + " interactions were ignores because " + nodesNotFound.size() + " nodes could not be mapped\n");
		
		String str = "\nGenes not found:\n";
		for(String s : nodesNotFound) {
			str = str + "\n" + s;
		}
		log.info(str + "\n");
		
		cleanUp();
		return graph;
	}
	
	private String createGeneNode(String[] r) {
		String geneName = r[index.get("Target gene_name")];
		String geneId = r[index.get("Target gene_Refseq_acc")];
		if(geneId.contains(".")) {
			geneId = geneId.substring(0, geneId.lastIndexOf("."));
		}
		GeneNode node = GeneNode.createGeneNode(geneId, geneName, idMapper, DataSource.getBySystemCode("Q"));
		if(node != null) {
			if(!genes.containsKey(node.getId())) {
				genes.put(node.getId(), node);
			}
			return node.getId();
		} else {
			if(!nodesNotFound.contains(geneId)) {
				nodesNotFound.add(geneId);
			}
			return null;
		}	
	}
	
	private String createMiRNANode(String[] r) {
		String miRNA = processMirna(r[index.get("miRNA_mature_ID")], r[index.get("miRNA_species")]);
		
		MiRNANode node = MiRNANode.createMiRNANode(miRNA, idMapper, DataSource.getBySystemCode("Mb"));
		if(node != null) {
			if(!miRNAs.containsKey(node.getId())) {
				miRNAs.put(node.getId(), node);
			}
			return node.getId();
		} else {
			if(!nodesNotFound.contains(miRNA)) {
				nodesNotFound.add(miRNA);
			}
			return null;
		}
	}
	

	private void cleanUp() {
		index.clear();
		miRNAs.clear();
		genes.clear();
		interactions.clear();
		nodesNotFound.clear();
		interactionIgnored.clear();
	}

	private void setNetworkAttributes(File input) {
		graph.setTitle("miRecords 2010-11-25");
		graph.setAttribute(CommonAttributes.DATABASE.getName(), "miRecords 2010-11-25 (" + pargs.getOrganism() + ")");
		graph.setAttribute(CommonAttributes.TYPE.getName(), "MTI");
		graph.setAttribute(CommonAttributes.IDENTIFIERS.getName(), "identifiers");
		graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), input.getName());
		graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "RefSeq");
		graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "miRBase");
		graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "mirna");
		graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
	}
	
	private String processMirna(String mirna, String species) {
		mirna = mirna.replace("[", "");
		mirna = mirna.replace("]", "");

		if (mirna.startsWith("miR")) {
			// Add species code
			String code = SpeciesCodes.getCode(species);
			if (code != null)
				mirna = code + "-" + mirna;
		}
		return mirna;
	}

	/**
	 * Removes all invalid Unicode characters that are not suitable to be used
	 * either in markup or text inside XML Documents.
	 * 
	 * Based on these recommendations
	 * http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
	 * http://cse-mjmcl.cse.bris.ac.uk/blog/2007/02/14/1171465494443.html
	 * 
	 * @param s: The resultant String stripped of the offending characters!
	 * @return
	 */
	public String removeInvalidXMLCharacters(String s) {
		StringBuilder out = new StringBuilder();

		int codePoint;
		int i = 0;

		while (i < s.length()) {
			// This is the unicode code of the character.
			codePoint = s.codePointAt(i);
			if ((codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD)
					|| ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
					|| ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
					|| ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) {
				out.append(Character.toChars(codePoint));
			}
			i += Character.charCount(codePoint);
		}
		return out.toString();
	}
	
	private File getLogFile() {
		if(pargs.isLogFile()) {
			return pargs.getLogFile();
		} else {
			if(pargs.isInput()) {
				return new File(pargs.getInput() + ".log");
			} else {
				return null;
			}
		}
	}
}
